基础点:

Control.js

控件基类模块,该类不可以直接使用,经过继承之后,形成更加具体的按钮之类的控件才使用,可以认为就是一个控件抽象基类。

包含如下一些自有属性:

  • type :控件的类型,比如 Button 、 Input 、 Form 、 Calendar 等等
  • skin :控件的皮肤,仅在初始化时设置有效,运行时不得变更
  • styleType :控件的样式类型,用于生成各class使用,如无此属性,则使用 Control#type 属性代替
  • id : 控件的 id

这些属性( property )均可在 html 代码中设置,比如:

1
<div data-ui-type="Button"></div>

还有另外一部分自有属性,这些属性不能用于 html 代码设置:

  • helper :控件常用的一些方法组成的一个对象属性
  • children :子控件数组
  • childrenIndex
  • currentStates
  • domEvents
  • main : 控件的主元素, HTMLElement 类型

原型( Control.prototype )上面有一些对象属性:

  • ignoreStates : 指定在哪些状态下该元素不处理相关的DOM事件

控件的生命周期中,有如下状态:

  • NEW : 在进入构造函数后,控件的状态就是 NEW 了
  • INITED : 控件完成 options 初始化( initOptions() )、视图环境初始化( initViewContext() )、扩展初始化( initExtensions() )之后状态就是 INITED 了
  • RENDERED : 控件第一次调用 render() 方法之后,就转变为 RENDERED 了
  • DISPOSED : 控件处于非 DISPOSED 状态下,调用 destroy() 方法,就变成了 DISPOSED 状态了

Control 上有一个重要的方法 render() ,用于渲染控件,该方法会去调用 repaint() 方法。

另外,Control 上还有一些 DOM 操作的方法,比如 appendTo() 、 insertBefore() 等。

Control 上的 get() 和 set() 很有猫腻。举个例子,如果这样调用 get() 方法: get('some-title') ,首先会去检测当前实例上面有没有 getSomeTitle() 方法,如果有,则直接调用这个方法,返回这个方法的返回值;如果没有,则直接返回当前对象的 some-title 属性。 set() 方法也是类似的。

setProperties() 方法可以用来批量设置属性,它会对一些特殊属性进行处理、控制,比如:

  • 只有在渲染以前(就是 initOptions() 调用的那次)才允许设置 id 属性
  • 如果要设置 viewContext ,则直接调用 setViewContext() 设置
  • 有些属性要转换成布尔值,比如 disabled 、 hidden

setProperties() 也会对批量设置的值进行脏检测,如果发现有属性值发生了改变,则会调用 repaint() 方法。脏检测函数是 isPropertyChanged() , 默认只会用恒等号去判断是否变化,但是可以在子类中覆盖这个方法,实现自己想要的脏检测功能。

Button.js

按钮控件。主要有这么几种按钮:普通按钮、添加按钮、下载按钮、链接按钮、右上角关闭按钮。

可以对按钮设置皮肤( data-ui-skin ),内置的皮肤有: spring 、 spring-add 、 download 、 layerClose 、 link 。

可以禁用掉按钮( data-ui-disabled=”diabled” )。

按钮上 DOM 相关的事件只有 click 。由于按钮是间接继承自 EventTarget ,所以可以使用 on 、 un 等方法处理事件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 给按钮绑定事件处理函数
someButton.on('click', handler);
// 5s 后取消绑定事件绑定
setTimeout(function () {
someButton.un('click', handler);
}, 5000);
// 只执行一次的回调函数
someButton.once('click', function () {
// only once
});
function handler(event) {
// do something
}

具体按钮 demo 可参看此处

Validator

数据验证模块,主要有三个基础类 Validity 、 ValidityState 、 Rule 。

ValidityState 表示某个控件的某一条验证规则的状态(是否验证通过),有两个自有属性:

  • state : 验证状态, true 为值合法, false 为值非法
  • message : 验证信息,比如说错误提示语

Rule 是验证规则基类,是对 InputControl 的值的验证逻辑的抽象。每一个验证规则都包含一个 check(value, control) 方法,该方法返回一个 ValidityState 对象以表示验证结果。验证规则必须通过 main.registerRule() 进行注册后才可生效。每一个验证规则包含 prototype.type 属性来确定规则的类型。验证规则并不会显式地附加到控件上,而是通过控件自身的属性决定哪些规则生效,当控件本身具有与规则的type属性相同的属性时,此规则即会生效,例如:

1
2
var textbox = main.create('TextBox', { maxLength: 30 });
textbox.validate();

由于 textbox 上存在 maxLength 属性,因此 MaxLengthRule 会对其进行验证,此特性可以从 main.createRulesByControl() 方法中看出。

Validity 主要用于存放一系列验证结果( ValidityState ),如果验证失败,则会触发 InputControl 的 invalid 事件,该事件会带上一个 Validity 对象作为参数。

InputControl.js

输入控件基类模块,用于表示需要在表单中包含的控件,主要提供 getRawValue()getValue() 方法供获取值。该类是一个抽象类,不应该直接使用。

需要注意的是,控件其实并不通过严格的继承关系来判断一个控件是否为输入控件,只要 getCategory() 返回为 "input""check" 或“extend”` 就认为是输入控件。

相比普通控件的 禁用 / 启用 ,输入控件共有3种状态:

  • 普通状态:可编辑,值随表单提交
  • disabled :禁用状态,此状态下控件不能编辑,同时值不随表单提交
  • readOnly :只读状态,此状态下控件不能编辑,但其值会随表单提交

setValue() 和 getValue() 分别用于设置输入控件的值和获取输入控件的值。 getRawValue() 和 setRawValue() 用于处理控件原始值,原始值的格式由控件自身决定。这两对处理输入控件值的方法的主要区别是,setValue() 会先调用控件的 parseValue() (子类可重写此方法)方法转换传入的值,然后再调用 setRawValue() 设置到控件上面去, getValue() 也会调用控件的 stringifyValue() (子类可重写此方法)将 getRawValue() 得到的值转换后返回。

getValidationResult() 方法用于获取此控件数据验证结果。

BoxGroup.js

选择框组控件的各种使用可参见此处

CheckBox.js

CheckBox 控件在初始化时可以提供 datasource 属性,该属性用于控件判断一开始是否选中,且这个属性只在初始化时有效,不会保存下来。datasource可以是以下类型:

  • 数组:此时只要rawValuedatasource中(使用==比较)则选上
  • 其它:只要rawValue与此相等(使用==比较)则选上

示例

ControlCollection.js

控件集合,类似 jQuery 对象的功能,提供便携的方法来访问和修改一个或多个控件。

ControlCollection 提供 Control 的所有公有方法,但没有任何保护或私有方法。

对于方法, ControlCollection 采用 Write all, Read first 的策略,需要注意的是,类似 setProperties() 的方法虽然有返回值,但被归类于写操作,因此会对所有内部的控件生效,但只返回第一个控件执行的结果。

ControlCollection 仅继承 Control 的方法,并不包含任何子类独有方法,因此无法认为集合是一个 InputControl 而执行如下代码:

1
collection.setValue('foo');

此时可以使用通用的 set() 方法来代替:

1
collection.set('value', 'foo');

根据 set() 方法的规则,如果控件存在 setValue() 方法,则会进行调用。

ViewContext.js

视图环境类,一个视图环境是一组控件的集合,不同视图环境中相同 id 的控件的 DOM id 不会重复。

该类的实例包含的主要属性为:

  • controls :该视图环境下所有的控件
  • groups :视图环境控件分组集合
  • id :视图环境 id,只读

当前页面所有的视图环境对象都会以id->对象的形式保存在私有的 pool 变量中。

painters.js

渲染器模块,负责 dom 渲染。

可以生成各种各样的渲染器( painter )。例如:

  • painters.state():生成一个将属性与控件状态关联的渲染器
  • painters.attribute():生成一个将控件属性与控件主元素元素的属性关联的渲染器
  • painters.style():生成一个将控件属性与控件主元素元素的样式关联的渲染器
  • painters.html():生成一个将控件属性与某个DOM元素的HTML内容关联的渲染器
  • painters.text():生成一个将控件属性与某个DOM元素的HTML内容关联的渲染器
  • painters.delegate():生成一个将控件属性的变化代理到指定成员的指定方法上

借助于这些方法,可以为某一个控件生成一组渲染器,当该控件发生了变化(样式属性变化等),相应的渲染器就会被调用,从而保证了数据与界面的一致性,形成了单向数据流,同时也比较细腻度地更新指定界面部分,不会出现全局刷新的情况。

Extension.js

扩展基类,针对控件的扩展。

Extension 类为扩展基类,所有扩展类需要继承于 Extension 。扩展类需要通过 main.registerExtension 方法,注册扩展类型。注册扩展类型时将自动根据 prototype.type 进行类型关联。

一个控件实例可以组合多个Extension实例,但一个控件实例对同种类型(即 type 相同)的 Extension ,只能拥有一份。

从设计上而言, Extension 不同于普通脚本对控件的操作,相比 ESUI 从设计理念上不希望普通脚本操作控件的保护属性及内部DOM元素,扩展则对控件拥有完全开放的权限,这包含但不限于:

  • 注册事件、修改属性等其它逻辑程序可做的行为。
  • 覆盖控件实例上的相应函数,如 render()addChild() 等。
  • 读取核心属性关键属性,包括 typemain 等。
  • 可接触控件内部的 DOM 对象,即可以访问 main 及其子树,并对 DOM 做任何操作。

在控件初始化时,会对扩展进行初始化,其基本流程为:

1、 当控件 init 之后,会依次对所有关联 Extension ,调用 attachTo() 方法。一个类型的 Extension 仅能在控件实例上附加一次,如果一个控件已经附加了同类型的 Extension 实例,则跳过本次 attachTo 操作。
2、 当控件 dispose 之前,会依次对所有关联 Extension ,调用其 dispose() 方法。

有多种方法可以将扩展绑定到具体的控件实例上:

  • 在控件创建时绑定

    通过控件构造函数参数 options.extensions 可以为控件绑定扩展。

    1
    2
    3
    4
    5
    6
    new TextBox({
    extensions: [
    new MyExtension({ ... }),
    new OtherExtension({ ... })
    ]
    });
  • 在使用HTML生成时绑定

    在HTML中,使用 data-ui-extension-xxx 属性注册一个扩展:

    1
    2
    3
    4
    5
    6
    <div id="main-panel" class="wrapper"
    data-ui-type="Panel"
    data-ui-extension-command-type="Command"
    data-ui-extension-command-events="click,keypress,keyup"
    data-ui-extension-command-use-capture="false"
    </div>

    在HTML中,使用 data-ui-extension-*-property 属性添加扩展,其中*作为扩展的分组,可以是任何字符串,相同的*将作为对同一扩展的定义,必须包含 data-ui-extension-*-type 定义扩展的类型,而其它 data-ui-extension-*-property="value" 属性则将作为 options 参数的属性传递给扩展的构造函数。

  • 在实例创建后动态地绑定

    在控件创建后,可以动态创建扩展并在适当的时候绑定至控件。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var panel = new Label({ text: 'abc' });
    var delegateDOMEvents = main.createExtension(
    'Command',
    {
    eventTypes: ['click', 'keypress', 'keyup'],
    useCapture: false
    }
    );
    // 需主动调用attachTo方法
    delegateDomEvents.attachTo(panel);
    panel.appendTo(container);
  • 全局绑定

    调用{@link main#attachExtension}函数可在全局注册一个扩展:

    1
    main.attachExtension('Command', { events: ['click'] });

    全局注册的扩展,将会被附加到所有控件的实例上。使用 options 参数作为 Extension 创建时的选项,创建 Extension 实例时会对 options 做复制处理。

    具体可以参考 extension.Command 作为示例,来学习扩展的编写。

src/main.js 中的 main.init() 方法

该方法是整个 esui 的入口方法,可以指定当前要使用 esui 的 dom 节点容器,类似于 angular.bootstrap() 或者 React.render(),都是以指定的 dom 元素为根,然后开始渲染这块地盘。具体参数传递可参看文档。

该方法会返回一个这个块地盘初始化的控件对象集合,例如:

1
2
3
4
5
6
7
<div id="container">
<button data-ui="type:Button;id:defaultBtn;">默认按钮</button>
<span data-ui="type:Button;id:springBtn;skin:spring">创建</span>
<div data-ui="type:Button;id:springAddBtn;skin:spring-add">添加</div>
<div data-ui="type:Button;id:downloadBtn;skin:download">下载</div>
<div data-ui="type:Button;id:actBtn;">改变文字</div>
</div>

此 container 初始化后就会返回五个控件对象控件的集合。

类关系

  • ControlCollection
    • ControlGroup
  • Extension
  • EventTarget
    • Control
      • Button
      • CommandMenu
      • Crumb
      • InputControl
        • BoxGroup
        • Calendar
        • CheckBox
        • RangeCalendar
        • Region
        • RichCalendar
        • Schedule
        • Select
        • TextBox
        • TextLine
      • Dialog
      • Label
      • Frame
      • Pager
      • Panel
        • Form
        • Overlay
      • SearchBox
      • Sidebar
      • Tab
      • Table
      • Tip
      • TipLayer
      • Toast
      • Tree
      • Validity
      • Wizard
    • Layer
    • Link
    • MonthView
  • SafeWrapper
  • TreeStrategy
  • ViewContext

一些总结

绝大多数控件在源码中其实都有比较详尽的说明了,只要仔细看看注释,再结合相关代码,很快就会用了。不过,在看代码的时候,以下几处务必留意:

  • 1、 initOption() 函数,该函数会初始化一些参数,很多都可以通过 data-ui-xxx 来设置,也可以通过 set() 方法来设置;
  • 2、 repaint 属性,该属性中存放了重绘相关的配置,留意会造成重绘的属性,这些属性往往也可以用上条所述方式设置;
  • 3、留意控件会触发什么事件,直接在源代码中搜索 fire( ,即可快速知道该控件会触发什么事件。